C# record perf + sentinel-based directive assembly#6755
Closed
jkschneider wants to merge 19 commits intomainfrom
Closed
C# record perf + sentinel-based directive assembly#6755jkschneider wants to merge 19 commits intomainfrom
jkschneider wants to merge 19 commits intomainfrom
Conversation
Implements C# parsing using Roslyn with round-trip printing: - Core LST types: CompilationUnit, UsingDirective, PropertyDeclaration, AccessorDeclaration, AttributeList - C#-specific expressions: NamedExpression, RefExpression, DeclarationExpression, CsLambda - Pattern matching: RelationalPattern, PropertyPattern with JContainer<NamedExpression> - Markers: DelegateInvocation, NullSafe (shared with Java), MultiDimensionalArray, NullCoalescing - 265 passing tests covering statements, expressions, and pattern matching
- Add tests for arrays, async, casts, enums, interfaces, interpolated strings - Add tests for locks, namespaces, records, structs, tuples, yield - Update parser, printer, and visitor for improved C# support
Build the RPC layer that enables Java to communicate with the C# parser over stdin/stdout using StreamJsonRpc. This includes: - Visitor hierarchy: JavaVisitor<P> base class, CSharpVisitor<P> extends it - RPC infrastructure in Rewrite.Core (RpcSendQueue, RpcObjectData, Reference) - JavaSender for serializing J-type AST nodes - CSharpSender with double-delegation pattern for Cs/J nodes - RPC server (RewriteRpcServer) with Parse, GetObject, Print endpoints - Java-side receiver, codec, and test infrastructure - Fix JavaReceiver.visitPrimitive to consume keyword via onChange - Improved error reporting in RpcReceiveQueue.receiveList
Resolve conflict in settings.gradle.kts by keeping both rewrite-csharp and rewrite-docker in the project list.
Adds 13 new tree types for C# preprocessor directives as first-class LST nodes: ConditionalBlock, IfDirective, ElifDirective, ElseDirective, PragmaWarningDirective, NullableDirective, RegionDirective, EndRegionDirective, DefineDirective, UndefDirective, ErrorDirective, WarningDirective, and LineDirective. Implements gap-scanning parser support in CSharpParser.cs that detects directive lines in whitespace gaps between members/statements. Conditional directives (#if/#elif/#else/#endif) are modeled but not yet parsed — they remain as Space for now. Full RPC round-trip support: visitor, printer, sender (C#), and receiver (Java) for all 13 types. Six new round-trip tests verify parse-print fidelity for #region, #pragma warning, #nullable, #warning, #line, and multi-code #pragma warning directives.
- Add TreeVisitor<T, P> base class, ExecutionContext, Recipe, ScanningRecipe<T>, Result
- Add RecipeDescriptor, OptionDescriptor, and [Option] attribute with reflection-based extraction
- Add [LanguageInjection("markdown")] annotations via JetBrains.Annotations NuGet
- Add RewriteTest/RecipeSpec test infrastructure with end-to-end rename class test
- Remove CSharpRpcCodec and static codec registry in favor of constructor-injected tree codec
- Consolidate multi-project C# solution into single OpenRewrite project
…on, and expression-bodied method support Delete Java-side CSharpPrinter and delegate all printing to C# via RPC, matching the architecture used by JavaScript and Python. Add parser and printer support for pattern matching (is-patterns with and/or/not, declaration patterns via StatementExpression wrapper), switch expressions, expression-bodied methods (ExpressionBodied marker on J.MethodDeclaration), and preprocessor directives. Introduce C# marker types (PrimaryConstructor, Implicit, Struct, RecordClass, ExpressionBodied) for syntactic features.
Implements the full recipe marketplace stack for C#: - C# core model: CategoryDescriptor, RecipeMarketplace, IRecipeActivator - RPC handlers: GetMarketplace, InstallRecipes, PrepareRecipe, Visit - NuGet package download via dotnet CLI (mirrors JS npm install pattern) - Java-side: NuGetRecipeBundleResolver/Reader, installRecipes client methods - Transitive dependency loading from NuGet cache via project.assets.json
Add SearchResult and Markup (Error, Warn, Info, Debug) marker types with RPC codec support. Implement MarkerPrinter interface with Default, SearchMarkersOnly, Fenced, and Sanitized modes. Wire up BeforeSyntax and AfterSyntax in CSharpPrinter to call marker printer hooks using the /*~~(content)~~>*/ comment wrapper format, matching the pattern used by rewrite-javascript and rewrite-python.
- Full JavaType model in C# with CSharpTypeMapping (Roslyn ISymbol → JavaType) - Bidirectional RPC receiver (JavaReceiver.cs, CSharpReceiver.cs) for tree exchange - RPC-based recipe execution: RpcVisitor delegates to Java peer via PrepareRecipe/Visit - UsesType/UsesMethod preconditions with local fallback (Preconditions.cs, LocalUsesType.cs) - 7 cross-language recipes registered for C# in recipes.csv - 9 recipe tests: changeMethodName, findMethods, findTypes, deleteMethodArgument, reorderMethodArguments, changeType, changePackage, plus type attribution verification - Fix CSharpParser.VisitType() to apply type attribution to user-defined types
Rename csharpProjectPath to csharpServerEntry to reflect that the entry point can be either a .csproj file (launched via dotnet run --project) or a published DLL (launched via dotnet <path>). This prepares for CLI integration where the server is distributed as a published artifact.
- Remove RoslynRecipe and EmbeddedResourceHelper (dead code, replaced by RPC) - Update target framework from net9.0 to net10.0 (net9.0 EOL Nov 2026) - Add NuGet tool package publishing: csharpUpdateVersion, csharpPack, csharpPublish tasks in build.gradle.kts with snapshot versioning - Make .csproj packable as dotnet tool (PackAsTool, OpenRewrite.CSharp)
Avoids modifying the .csproj file on every snapshot/release build, keeping the working tree clean.
- Add ParseProject RPC handler for .csproj-based parsing with NuGet resolution and source-generator file discovery - Add CSharpWhitespaceValidationService for detecting unparsed code in rewriteRun - Add J.NullableType (int?, string?) with full J pipeline - Add Cs.NullSafeExpression for null-forgiving operator (x!) - Add VisitDestructorDeclaration mapping to J.MethodDeclaration (~ClassName) - Add VisitRefExpression for standalone ref expressions (ref locals) - Add Cs.InitializerExpression for object/collection initializers - Add Cs.ExternAlias for extern alias directives - Add Cs.PointerType and VisitFixedStatement support - Add VisitLabeledStatement, VisitUnsafeStatement, VisitDefaultExpression, VisitSizeOfExpression, VisitLockStatement (as J.Synchronized) - Register OmitParentheses marker for RPC serialization - Simplify Cs.Keyword model in Java (remove unused enum values) - 50 tests passing (36 RPC + 5 ParseProject + 9 Recipe)
…eParameter Replace Cs.ClassDeclaration with J.ClassDeclaration for all C# type declarations. C#-specific type parameter data (variance, attributes, where-clause constraints) is now stored via a new ConstrainedTypeParameter type in J.TypeParameter.Bounds[0]. Deleted types: CsClassDeclaration, ClassDeclarationKind, CsTypeParameter, TypeParameterConstraintClause, TypeParameterBound, TypeConstraint, AllowsConstraint interface, TypeParameterConstraint interface. Constraint types (ClassOrStructConstraint, ConstructorConstraint, DefaultConstraint, etc.) now implement Expression instead of the deleted TypeParameterConstraint interface.
Map 21 additional Roslyn syntax types (delegates, events, indexers, operators, goto, using statements, anonymous methods/objects, with expressions, spread, function pointers, list/slice patterns, etc.), add 20 new RPC round-trip tests, and remove 16 unused Cs model types that the parser maps to J types instead.
Files with #if/#elif/#else/#endif are parsed once per unique symbol permutation, each producing a complete CompilationUnit. A new ConditionalDirective wraps all branches and a line-level interleaving printer reconstructs the original source. Replaces the old ConditionalBlock/IfDirective/ElifDirective/ElseDirective types which could not handle directives splitting code at arbitrary points.
Records generate value equality checks that compare every field on every visitor call, which is expensive for large ASTs. Switching to classes with explicit With* methods and id-based equality (matching the Java LST pattern) avoids this overhead.
… assembly The previous line-number-based approach stored absolute line positions in DirectiveLine.lineNumber and required all branches to have identical line counts. If a recipe added or removed lines within a branch, the printer produced corrupted output. Ghost comments (//DIRECTIVE:N) are now emitted in clean source in place of directive lines. These survive Roslyn parsing as whitespace text and act as positional sentinels. A DirectiveBoundaryInjector scans the parsed tree and attaches DirectiveBoundaryMarker to nodes adjacent to directive boundaries, giving recipes structural access to boundary positions. The printer splits each branch's output by the ghost comment pattern into sections, then assembles sections from the appropriate branch — allowing different branches to have different section sizes after recipe modifications.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
//DIRECTIVE:N) andDirectiveBoundaryMarkerTest plan
CSharpRpcTesttests pass, including all 8 directive tests (parseSimpleIfEndif, parseSimpleIfElse, parseKeywordSplittingDirective, parseNestedDirectives, parseElifDirective, parseDirectiveWithComplexCondition, parseDirectiveChangingBaseClass, parsePolyfillConditionalClass)